package com.anjubao.parking.municipal.user.service;

import com.anjubao.parking.municipal.common.exception.ServiceException;
import com.anjubao.parking.municipal.common.utils.BeanUtils;
import com.anjubao.parking.municipal.common.utils.RedisClientTemplate;
import com.anjubao.parking.municipal.common.utils.U;
import com.anjubao.parking.municipal.user.component.lock.DistributedLocker;
import com.anjubao.parking.municipal.user.component.sms.AliSmsTemplate;
import com.anjubao.parking.municipal.user.constants.BalanceTypeEnum;
import com.anjubao.parking.municipal.user.constants.VehicleOperateEnum;
import com.anjubao.parking.municipal.user.constants.VehicleTypeEnum;
import com.anjubao.parking.municipal.user.dto.*;
import com.anjubao.parking.municipal.user.form.UserBalancePayForm;
import com.anjubao.parking.municipal.user.form.UserInfoUpdateForm;
import com.anjubao.parking.municipal.user.form.VehicleBindForm;
import com.anjubao.parking.municipal.user.mapper.UserBalanceRecordMapper;
import com.anjubao.parking.municipal.user.mapper.UserInfoMapper;
import com.anjubao.parking.municipal.user.mapper.UserVehicleMapper;
import com.anjubao.parking.municipal.user.model.UserBalanceRecord;
import com.anjubao.parking.municipal.user.model.UserInfo;
import com.anjubao.parking.municipal.user.model.UserVehicle;
import com.anjubao.parking.municipal.user.utils.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 用户信息表Service
 *
 * @author zhuchunfeng
 */
@Service
@Slf4j
public class UserInfoService {

    //验证码缓存前缀key
    private static final String CAPTCHA_CACHE_KEY_PRE = "USER.SERVICE:captcha.";
    //16位用户号
    private static final String USER_CODE_GENERATE_KEY = "USER.SERVICE:generate.code";
    //二维码过期时间
    private static final int EXPIRED_TIME = 5;

    @Resource
    private UserInfoMapper userInfoMapper;
    @Resource
    private UserBalanceRecordMapper userBalanceRecordMapper;
    @Resource
    private RedisClientTemplate redisClientTemplate;
    @Resource
    private AliSmsTemplate aliSmsTemplate;
    @Resource
    private DistributedLocker distributedLocker;
    @Resource
    private UserVehicleMapper userVehicleMapper;

    /**
     * 获取用户二维码
     *
     * @param phone 手机
     * @return 二维码信息
     */
    public CaptchaDto getCaptcha(String phone) {

        //30秒内禁止重试
        boolean tryLock;
        try {
            tryLock = distributedLocker.tryLock("USER.SERVICE:UserInfoService.getCaptcha" + phone, 1, 30);
        } catch (Exception e) {
            tryLock = false;
        }

        if(tryLock){
            log.info("current Thread name: {}", Thread.currentThread().getName());
            //四位随机数
            String captcha = String.format("%04d", new Random().nextInt(10000));
            //5分钟
            redisClientTemplate.setEx(CAPTCHA_CACHE_KEY_PRE + phone, captcha, EXPIRED_TIME, TimeUnit.MINUTES);
            aliSmsTemplate.sendSms(phone, captcha);
        }
        return new CaptchaDto()
                .setCaptcha(redisClientTemplate.get(CAPTCHA_CACHE_KEY_PRE + phone))
                .setExpireTime(LocalDateTime.now().plusMinutes(EXPIRED_TIME))
                .setTimeInterval(EXPIRED_TIME);
    }

    /**
     * 校验当前手机号用户是否存在，不存在则创建用户，返回新创建的用户ID，已存在则返回用户对应的用户信息
     *
     * @param phone   手机号
     * @param captcha 验证码
     * @return 用户信息
     */
    public UserInfoDto validateCaptcha(String phone, String captcha) {
        String s = redisClientTemplate.get(CAPTCHA_CACHE_KEY_PRE + phone);
        if (!(StringUtils.isNotEmpty(s) && s.equals(captcha))) {
            throw new ServiceException("验证码有误");
        }
        UserInfoDto userInfoDto = new UserInfoDto();
        UserInfo userInfo = userInfoMapper.selectOne(new QueryWrapper<>(new UserInfo().setPhone(phone)));
        if (userInfo == null) {
            userInfo = this.buildAndInsertUser(phone);
            userInfoDto.setNewAccount(1); //新用户
            userInfoDto.setVehicles(new ArrayList<>());
        } else {
            userInfoDto.setNewAccount(0); //非新用户
            userInfoDto.setVehicles(this.getVehiclesByUserCode(userInfo.getId()));

        }
        return userInfoDto
                .setUnionId(userInfo.getUnionId())
                .setUserCode(IdUtil.getCode(userInfo.getId()));
    }


    /* 创建新用户*/
    private UserInfo buildAndInsertUser(String phone) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(this.generateCode());
        userInfo.setPhone(phone);
        userInfo.setName("");
        userInfo.setIdCard("");
        userInfo.setBalance(0);
        userInfo.setNickName("");
        userInfo.setAvatar("");
        userInfo.setWxNickname("");
        userInfo.setUnionId("");
        userInfo.setMinappOpenid("");
        userInfo.setWcOpenid("");
        userInfoMapper.insert(userInfo);
        return userInfo;
    }

    //获取16位唯一code
    private Long generateCode() {
        Long incr = redisClientTemplate.incr(USER_CODE_GENERATE_KEY);
        //如果小于16位
        if (incr < 1_000_000_000_000_000L) {
            Long userMaxCode = this.getUserMaxCode();
            redisClientTemplate.set(USER_CODE_GENERATE_KEY, userMaxCode);
            redisClientTemplate.persist(USER_CODE_GENERATE_KEY);
            incr = redisClientTemplate.incr(USER_CODE_GENERATE_KEY);
        }
        return incr;
    }

    //获取数据库当前最大用户code
    private Long getUserMaxCode() {
        IPage<UserInfo> userInfoIPage = userInfoMapper.selectPage(
                new Page<>(1, 1),
                new QueryWrapper<>(new UserInfo()).lambda().orderByDesc(UserInfo::getId));
        if (userInfoIPage.getTotal() == 0L || userInfoIPage.getRecords().get(0).getId() < 1_000_000_000_000_000L) {
            return 1_000_000_000_000_000L; //16位用户code
        }
        return userInfoIPage.getRecords().get(0).getId();

    }

    /**
     * 通过code查找用户,用户不存在返回null
     *
     * @param userCode 用户code
     * @return 用户信息
     */
    public UserDetailDto getUserDetailByUserCode(String userCode) {
        UserInfo userInfo = userInfoMapper.selectById(IdUtil.getId(userCode));
        if (userInfo == null) {
            return null;
        }
        UserDetailDto userDetail = BeanUtils.transform(UserDetailDto.class, userInfo);
        userDetail.setVehicles(this.getVehiclesByUserCode(userInfo.getId()));
        userDetail.setUserCode(userCode);
        return userDetail;
    }

    private UserDetailDto getUserDetailByUnionId(String unionId){
        List<UserInfo> userInfos = userInfoMapper.selectList(new QueryWrapper<>(new UserInfo().setUnionId(unionId)));
        if (CollectionUtils.isEmpty(userInfos)) {
            return null;
        }
        UserInfo userInfo = userInfos.get(0);
        UserDetailDto userDetail = BeanUtils.transform(UserDetailDto.class, userInfo);
        userDetail.setVehicles(this.getVehiclesByUserCode(userInfo.getId()));
        userDetail.setUserCode(IdUtil.getCode(userInfo.getId()));
        return userDetail;

    }

    /**
     * 通过userCode修改用户信息
     * 若用户不存在抛出异常
     */
    public UserDetailDto updateUserInfoByUserCode(UserInfoUpdateForm userInfoUpdateForm) {

        log.info("绑定用户信息:{}",U.json(userInfoUpdateForm));
        UserInfo userInfoUpdate = BeanUtils.transform(UserInfo.class, userInfoUpdateForm);
        UserInfo entity = userInfoUpdate.setId(IdUtil.getId(userInfoUpdateForm.getUserCode()));
        int i = userInfoMapper.updateById(entity);
        if (i != 1) {
            throw new ServiceException("用户不存在");
        }
        return this.getUserDetailByUserCode(userInfoUpdateForm.getUserCode());
    }

    /**
     * 通过unionID修改用户信息
     * 若用户不存在抛出异常
     */
    public UserDetailDto updateUserInfoByUnionId(UserInfoUpdateForm userInfoUpdateForm) {
        if (StringUtils.isBlank(userInfoUpdateForm.getUnionId())) {
            throw new ServiceException("unionId不能为空");
        }
        log.info("通过unionId 更新用户信息 参数: {}",U.json(userInfoUpdateForm));
        UserInfo userInfoUpdate = BeanUtils.transform(UserInfo.class, userInfoUpdateForm);
        int update = userInfoMapper.update(userInfoUpdate, new UpdateWrapper<>(new UserInfo().setUnionId(userInfoUpdateForm.getUnionId())));
        if (update == 0) {
            throw new ServiceException("用户不存在");
        }
        return this.getUserDetailByUnionId(userInfoUpdateForm.getUnionId());
    }

    /**
     * 用户余额充值,余额不足抛出异常
     *
     * @param userBalancePayForm 充值参数
     * @return 用户余额
     */
    @Transactional(rollbackFor = Exception.class)
    public UserBalanceDto balancePay(UserBalancePayForm userBalancePayForm) {


        Long userId = IdUtil.getId(userBalancePayForm.getUserCode());
        int amount = userBalancePayForm.getAmount();
        Integer type = userBalancePayForm.getType();
        String tradeOrderId = userBalancePayForm.getTradeOrderId();

        //增加用户级别的分布式锁
        RLock lock = distributedLocker.lock("lock.key:balance.add" + userId, 5);
        UserInfo userInfo = userInfoMapper.selectById(userId);
        int balance = BalanceTypeEnum.RECHARGE.getCode().equals(type) ? userInfo.getBalance() + amount : userInfo.getBalance() - amount;
        if (balance < 0) {
            distributedLocker.unlock(lock);
            throw new ServiceException("余额不足");
        }
        userInfoMapper.updateById(new UserInfo().setId(userId).setBalance(balance));
        distributedLocker.unlock(lock);

        UserBalanceRecord userBalanceRecord = new UserBalanceRecord();
        userBalanceRecord.setId(U.id());
        userBalanceRecord.setUserId(userId);
        //该字段增加唯一索引防止并发请求
        userBalanceRecord.setTradeOrderId(tradeOrderId);
        userBalanceRecord.setAmount(amount);
        userBalanceRecord.setType(type);
        userBalanceRecord.setRemark(BalanceTypeEnum.RECHARGE.getCode().equals(type)?"余额充值":"停车缴费");
        userBalanceRecordMapper.insert(userBalanceRecord);

        return new UserBalanceDto().setCreateTime(LocalDateTime.now()).setId(userBalanceRecord.getUserId());
    }

    /**
     * 通过用户code查询车牌信息
     *
     * @param userCode 用户code
     * @return 用户车牌信息
     */
    public UserInfoDto getUserVehicles(String userCode) {


        UserInfo userInfo = userInfoMapper.selectById(IdUtil.getId(userCode));
        if (userInfo == null) {
            return null;
        }
        UserInfoDto userInfoDto = new UserInfoDto();
        userInfoDto.setUserCode(userCode);
        userInfoDto.setVehicles(this.getVehiclesByUserCode(userInfo.getId()));
        return userInfoDto;
    }


    /**
     * 通过车牌号查询用户信息,用户不存在返回null
     *
     * @param vehicle 车牌号
     * @return 用户信息
     */
    public UserInfoDto getUserByVehicle(String vehicle) {
        UserVehicle userVehicles = userVehicleMapper.selectOne(new QueryWrapper<>(new UserVehicle().setVehicle(vehicle)));
        if (null == userVehicles) {
            return null;
        }
        UserInfo userInfo = userInfoMapper.selectById(userVehicles.getUserId());
        return new UserInfoDto()
                .setUserCode(IdUtil.getCode(userInfo.getId()))
                .setVehicles(this.getVehiclesByUserCode(userInfo.getId()));
    }

    /*  通过userId查询车牌信息 */
    private  List<UserVehicleDto> getVehiclesByUserCode(Long userId){
        List<UserVehicle> userVehicles = userVehicleMapper.selectList(new QueryWrapper<>(new UserVehicle().setUserId(userId)));
        return userVehicles.stream().map(t -> BeanUtils.transform(UserVehicleDto.class, t)).collect(Collectors.toList());
    }

    /**
     * 绑定/解绑 车牌
     * @param vehicleBind 请求参数
     */
    public UserInfoDto bindVehicle(VehicleBindForm vehicleBind) {

        Long userId = IdUtil.getId(vehicleBind.getUserCode());
        String vehicle = vehicleBind.getVehicle();

        //绑定
        if (VehicleOperateEnum.BIND.getCode().equals(vehicleBind.getOperate())) {
            UserInfo userInfo = userInfoMapper.selectById(userId);
            if (null == userInfo) {
                throw new ServiceException("用户不存在");
            }
            List<UserVehicle> userVehicles = userVehicleMapper.selectList(new QueryWrapper<>(new UserVehicle().setUserId(userId)));
            if (userVehicles.size() >= 6) {
                throw new ServiceException("你绑定的车牌已超过6个，可解绑车牌后继续绑定新车牌。");
            }
            if (userVehicles.stream().map(UserVehicle::getVehicle).collect(Collectors.toSet()).contains(vehicleBind.getVehicle())) {
                throw new ServiceException("你引进绑定该车牌，请不要重复绑定。");
            }
            UserVehicle userVehicle = userVehicleMapper.selectOne(new QueryWrapper<>(new UserVehicle().setVehicle(vehicle)));
            if (null != userVehicle) {
                throw new ServiceException("车牌号已被其他用户绑定,如有疑问请联系客服");
            }
            Integer vehicleType = vehicleBind.getVehicleType();
            if(!VehicleTypeEnum.exist(vehicleType)){
                throw new ServiceException("无该车牌类型");
            }
            UserVehicle entity = new UserVehicle();
            entity.setId(U.id());
            entity.setUserId(userId);
            entity.setVehicle(vehicle);
            entity.setVehicleType(vehicleType);
            entity.setNote("");
            userVehicleMapper.insert(entity);
        }
        //解绑
        else {
            int delete = userVehicleMapper.delete(new UpdateWrapper<>(new UserVehicle().setUserId(userId).setVehicle(vehicle)));
            if (delete == 0) {
                throw new ServiceException("用户未绑定该车牌");
            }
        }
        return new UserInfoDto().setUserCode(vehicleBind.getUserCode());
    }


}
